/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
 *
 */

import { execSync, spawn } from 'child_process';
import cluster from 'cluster';
import { randomBytes } from 'crypto';
import fs from 'fs';
import path from 'path';

import { isWindows, isLinux, isMac } from '@ohos/hvigor-common';
import Websocket from 'ws';

import { iconv } from '../../../common/util/iconv/index.js';
import { HvigorLogger } from '../../log/hvigor-log.js';
import { HvigorConfigReader } from '../../util/hvigor-config-reader.js';
import { SessionIdHelper } from '../session/session-id.js';
import { readJavaDaemonInfoFromCacheFile, writeJavaDaemonInfo2CacheFile, writeSecretKeyFile } from './java-daemon-util.js';
const _log = HvigorLogger.getLogger('JavaDaemon');

const DEFAULT_START_PORT = 45050;

const charSet = isWindows() ? 'GBK' : 'UTF-8';

const HVIGOR_JAVA_DAEMON_JAR = 'hvigor-java-daemon.jar';

const HVIGOR_JAVA_DAEMON_JAR_PATH = path.resolve(__dirname, '../../../../lib', HVIGOR_JAVA_DAEMON_JAR);

const jvmXmxValue = Number(process.env.Xmx) || HvigorConfigReader.getHvigorConfig()?.javaOptions?.Xmx || 512;

function tryStartDaemonAtPort(libPath: string, port: number, tryCount: number) {
  const sessionId = randomBytes(32).toString('hex');
  const encryptSessionId = SessionIdHelper.encryptPwdByDefaultMaterial(sessionId, _log);
  const javaProcess = spawn('java', [`-Xmx${jvmXmxValue}m`, '-jar', libPath, port.toString(), encryptSessionId], {
    windowsHide: true,
  });

  javaProcess.on('spawn', () => {
    _log.debug(`java daemon started at port ${port} pid ${javaProcess.pid}`);
    try {
      writeJavaDaemonInfo2CacheFile({ port: port, pid: javaProcess.pid, sessionId: encryptSessionId });
      const secretKey = randomBytes(32).toString('hex');
      writeSecretKeyFile(secretKey);
    } catch (e) {
      _log.error(`java daemon writeJavaDaemonInfo2CacheFile failed ${e}`);
    }
  });

  javaProcess.on('error', (err) => {
    _log.debug(`on error: ${err}`);
  });

  javaProcess.stdout.on('data', (data) => {
    // no op
  });

  javaProcess.stderr.on('data', (data) => {
    _log.debug('javaProcess on stderr');
    if (data.toString().indexOf('java.net.BindException: Address already in use') !== -1) {
      port++;
      tryCount++;
      if (tryCount < 10) {
        tryStartDaemonAtPort(libPath, port, tryCount);
      }
    }
  });

  javaProcess.on('exit', (code, signal) => {
    _log.debug(`java daemon exit code:${code} signal:${signal}`);
  });

  process.on('exit', () => {
    _log.debug(`master process exit pid ${process.pid}`);
    if (javaProcess.pid) {
      try {
        process.kill(javaProcess.pid); // 杀死java子进程
        _log.debug(`kill java process pid ${javaProcess.pid}`);
      } catch (e) {
        _log.error(`kill java process failed ${e}`);
      }
    }
  });
}

function tryConnect(port: number, sessionId: string) {
  return new Promise((resolve, reject) => {
    const ws = new Websocket(`ws://127.0.0.1:${port}?sessionId=${sessionId}`);
    ws.on('error', function (err) {
      reject(err);
    });
    ws.on('open', function () {
      ws.close();
      resolve('');
    });
  });
}

function execSystemCommand(command: string): string | undefined {
  try {
    const result = execSync(command, {
      windowsHide: true,
      encoding: 'buffer',
      stdio: 'pipe',
    });
    return iconv.decode(<Buffer>result, charSet);
  } catch (e: any) {
    if (e.stderr) {
      const stderr = iconv.decode(<Buffer>e.stderr, charSet);
      _log.warn(stderr);
    } else {
      _log.warn(e);
    }
  }
  return undefined;
}

function killByPid(pid: string) {
  const stdout = execSystemCommand(`ps -fp ${pid}`);
  if (!stdout) {
    return;
  }
  if (stdout.includes('java -jar') && stdout.includes(HVIGOR_JAVA_DAEMON_JAR) && !stdout.includes(HVIGOR_JAVA_DAEMON_JAR_PATH)) {
    try {
      process.kill(Number(pid), 'SIGKILL');
    } catch (e) {
      _log.warn(e);
    }
  }
}

function killByGrep() {
  const stdout = execSystemCommand(`ps -ef | grep ${HVIGOR_JAVA_DAEMON_JAR}`);
  if (!stdout) {
    return;
  }
  const stdoutArray = stdout.split('\n');
  for (const output of stdoutArray) {
    if (output.includes('java -jar') && !output.includes(HVIGOR_JAVA_DAEMON_JAR_PATH)) {
      const outputArray = output.trim().split(/\s+/);
      if (outputArray.length < 2) {
        continue;
      }
      const pid = outputArray[1];
      try {
        process.kill(Number(pid), 'SIGKILL');
      } catch (e) {
        _log.warn(e);
      }
    }
  }
}

export function startJavaDaemon() {
  if (cluster.isMaster) {
    if (!fs.existsSync(HVIGOR_JAVA_DAEMON_JAR_PATH)) {
      _log.error(`${HVIGOR_JAVA_DAEMON_JAR} not exist at path ${HVIGOR_JAVA_DAEMON_JAR_PATH}`);
      return;
    }

    const javaDaemonInfo = readJavaDaemonInfoFromCacheFile();

    /**
     * 只能连接到当前IDE的 java daemon, 不能连接到其它IDE里面起的 java daemon.
     * 因为其它IDE里面的 java daemon 可能不是同一个用户起的，可能没有权限操作本IDE目录。
     */
    if (isLinux() || isMac()) {
      if (javaDaemonInfo?.pid) {
        killByPid(javaDaemonInfo.pid);
      } else {
        killByGrep();
      }
    }

    if (javaDaemonInfo?.port && javaDaemonInfo?.sessionId) {
      tryConnect(javaDaemonInfo.port, javaDaemonInfo?.sessionId).then(
        () => {
          _log.debug('java daemon tryConnect success');
        },
        (err) => {
          _log.debug(`java daemon tryConnect failed ${err}`);
          tryStartDaemonAtPort(HVIGOR_JAVA_DAEMON_JAR_PATH, DEFAULT_START_PORT, 0);
        },
      );
    } else {
      tryStartDaemonAtPort(HVIGOR_JAVA_DAEMON_JAR_PATH, DEFAULT_START_PORT, 0);
    }
  }
}
